Skip to content

OCPBUGS-98465: prevent DS crash in pull secret verifier and add propagation diagnostics - #8991

Merged
openshift-merge-bot[bot] merged 1 commit into
openshift:mainfrom
jparrill:OCPBUGS-98465
Jul 22, 2026
Merged

OCPBUGS-98465: prevent DS crash in pull secret verifier and add propagation diagnostics#8991
openshift-merge-bot[bot] merged 1 commit into
openshift:mainfrom
jparrill:OCPBUGS-98465

Conversation

@jparrill

@jparrill jparrill commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

The kubelet-config-verifier DaemonSet was timing out (0/3 ready for 20 minutes) across multiple platforms. Root cause confirmed empirically with @csrwng and @devguyio:

The ignition server rewrites the pull secret as compact JSON, while HCCO syncs original-pull-secret in whatever format the user supplied (often pretty-printed). The syncer handles this correctly — it uses bytes.TrimRight(content, "\n") before comparing (#7638). But the verifier's readiness probe compared raw md5sum hashes without trimming, so the trailing newline from ignition caused a permanent hash mismatch even though the syncer considered them equal.

Changes

  1. Trim newlines before hashing: pipe through tr -d '\n' before md5sum in both the readiness probe and the diagnostic logging loop, matching the syncer's comparison behavior
  2. Mount original-pull-secret instead of static copy: the previous approach copied openshift-config/pull-secret to kube-system at creation time, which became stale when t.Cleanup restored the original. Mounting original-pull-secret directly tracks the live feature state
  3. Move t.Cleanup to parent test: the pull secret restore now runs after all subtests complete, so the dummy entry stays in place during the verifier subtest
  4. Wait for syncer DS rollout: waitForDaemonSetRollout ensures syncer pods are fully updated and ready before deploying the verifier
  5. Add fallback sentinels: || echo FAIL_NODE / || echo FAIL_CLUSTER prevents false positives from empty strings when files are missing
  6. Add UpdatedNumberScheduled check: waitForDaemonSetReady now detects in-progress rollouts
  7. Separate DS readiness into own subtest: correct failure attribution
  8. Stage-by-stage diagnostic logging: propagation wait logs which stage is pending

Root Cause

Confirmed by @csrwng on a live cluster: the ignition server writes /var/lib/kubelet/config.json as trimmed compact JSON. HCCO creates kube-system/original-pull-secret from the user-supplied pull secret, which may be pretty-printed with trailing newlines. The syncer's bytes.TrimRight makes them compare equal, so it doesn't rewrite the on-disk file. The verifier's raw md5sum sees different bytes → readiness probe never passes.

Fixes

Test plan

  • Root cause confirmed empirically on live 4.22 HostedCluster
  • @csrwng verified the ignition rewrite behavior locally
  • e2e-aws presubmit passes (TestCreateCluster/Main/EnsureGlobalPullSecret)
  • e2e-aws-4-22 presubmit passes
  • e2e-aks presubmit passes

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@jparrill

Copy link
Copy Markdown
Contributor Author

/test e2e-aws

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The end-to-end pull-secret test now verifies propagation through hosted-control-plane and guest namespaces in separate polling stages. Kubelet verification uses a DaemonSet readiness probe to compare MD5 hashes between the node-mounted kubelet pull-secret and cluster pull-secret, then waits for all verifier pods to become Ready.

Sequence Diagram(s)

sequenceDiagram
  participant ManagementCluster
  participant HostedControlPlane
  participant GuestCluster
  participant VerifierDaemonSet
  participant KubeletNode
  ManagementCluster->>HostedControlPlane: Sync dummy pull-secret entry
  HostedControlPlane-->>GuestCluster: Propagate pull-secret entry
  GuestCluster->>GuestCluster: Verify pull-secret resources
  VerifierDaemonSet->>KubeletNode: Run readiness-probe MD5 comparison
  KubeletNode-->>VerifierDaemonSet: Report hash match
  VerifierDaemonSet-->>GuestCluster: Become Ready
Loading

Possibly related PRs

Suggested reviewers: ironcladlou, nirshal


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
No-Weak-Crypto ❌ Error globalps.go still uses md5sum in the verifier container and readiness probe to compare pull-secret and kubelet config hashes, which is disallowed weak-crypto usage. Replace md5sum-based checks with a non-crypto byte comparison (e.g. cmp -s) or a stronger hash; avoid shell equality on hash strings for secret verification.
Container-Privileges ❌ Error FAIL: the new kubelet-config-verifier DaemonSet sets SecurityContext.Privileged: true on its container. Drop privileged mode or document/justify it and use least-privilege settings instead.
Test Structure And Quality ⚠️ Warning The new readiness probe uses md5sum|cut and can falsely report Ready if both reads fail, so the DaemonSet readiness assertion is not reliable. Switch the probe to cmp -s (or another check that fails on unreadable files) so Ready truly means the files match; keep the current cleanup/timeouts.
✅ Passed checks (8 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed The changed subtests use static t.Run names; no generated names, timestamps, namespaces, UUIDs, or other dynamic values appear in titles.
Topology-Aware Scheduling Compatibility ✅ Passed Only verification logic changed; no nodeSelector/affinity/spread or replica logic was added, and the DaemonSet’s scheduling is unchanged.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo test bodies were added; the changed helpers only stage secret polling and DaemonSet readiness, with no IP literals or external network calls.
No-Sensitive-Data-In-Logs ✅ Passed Changed logs only mention namespaces, readiness status, and md5 hashes; no raw passwords, tokens, API keys, PII, or hostnames are emitted.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing the pull secret verifier DaemonSet behavior and adding propagation diagnostics.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@openshift-ci openshift-ci Bot added the area/testing Indicates the PR includes changes for e2e testing label Jul 13, 2026
@openshift-ci
openshift-ci Bot requested review from Nirshal and ironcladlou July 13, 2026 16:10
@openshift-ci openshift-ci Bot added approved Indicates a PR has been approved by an approver from all required OWNERS files. and removed do-not-merge/needs-area labels Jul 13, 2026
@jparrill

Copy link
Copy Markdown
Contributor Author

/hold until we find the underneath issue which is causing the flake.

@openshift-ci openshift-ci Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 13, 2026
@jparrill jparrill changed the title test(OCPBUGS-98465): add diagnostic logging to pull secret propagation test OCPBUGS-98465: add diagnostic logging to pull secret propagation test Jul 13, 2026
@openshift-ci-robot openshift-ci-robot added jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. labels Jul 13, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@jparrill: This pull request references Jira Issue OCPBUGS-98465, which is valid. The bug has been moved to the POST state.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state New, which is one of the valid states (NEW, ASSIGNED, POST)

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

Summary

Add diagnostic logging to the EnsureGlobalPullSecret pull secret propagation test. This is NOT a fix — we don't have sufficient evidence to determine the root cause yet.

Fixes

Why this PR exists

The pull secret propagation test fails ~1/14 runs but provides zero diagnostic information when it does. The Eventually loops silently swallow all errors, making it impossible to distinguish between:

  1. HO didn't sync — the Secret never reaches the CP namespace
  2. HCCO didn't reconcile — the CP namespace has the update but the guest cluster doesn't
  3. KAS unreachable — the propagation succeeded but guestClient.Get() fails because the guest API server is down

Evidence from the single observed failure (build 2075336365863604224, Jul 9) shows KAS i/o timeout errors on the same guest cluster during the test window, suggesting scenario 3 — but we can't confirm without diagnostic data.

What this PR changes

  • Stage 1 check: On each retry, reads the pull secret from the CP namespace and logs whether the HO has synced the dummy entry
  • Stage 2 check: Logs the guestClient.Get() error instead of silently returning false
  • Timeout: 150s → 5min defensively (with 10s polling instead of 5s to reduce log noise)
  • Same changes applied to both openshift-config/pull-secret and kube-system/original-pull-secret waits

What happens next

Once this merges, we wait for the test to fail again. The logs will tell us exactly where the propagation stalls, and we'll file the real fix.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

  • Improved verification that global pull-secret updates propagate correctly across control-plane and guest environments.

  • Added clearer staged checks and logging to help identify propagation delays or failures without requiring a NodePool rollout.

  • Tests

  • Strengthened end-to-end coverage for in-place pull-secret updates and synchronization across supported locations.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
test/e2e/util/util.go (2)

2083-2083: 🧹 Nitpick | 🔵 Trivial

Worst-case runtime for this subtest jumps meaningfully.

Combined, the two sequential Eventually calls can now take up to 5 minutes each (10 minutes total) versus the prior 150s-based timeouts, on top of the rest of the already-long EnsureGlobalPullSecret flow. Since this change is explicitly diagnostic-only (per PR objective) and not a fix for the underlying flake, worth confirming the team is comfortable with the added CI time budget this could add on every run of this e2e test, especially if it needs to hit the full timeout repeatedly across retries.

Also applies to: 2094-2094

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/util/util.go` at line 2083, Reduce the timeouts for the sequential
Eventually calls in EnsureGlobalPullSecret, including the assertion for
openshift-config/pull-secret propagation, so their combined worst-case duration
does not substantially increase the e2e test runtime. Preserve the diagnostic
behavior and existing polling interval while restoring the prior overall timeout
budget.

2061-2094: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated poll-and-check pattern into a helper.

The Get + bytes.Contains(..., []byte("e2e-dummy.example.com")) pattern is repeated three times (CP namespace secret, guest openshift-config/pull-secret, guest kube-system/original-pull-secret) with the dummy marker string duplicated as a literal in each spot. A small shared helper would reduce duplication and centralize the marker string, lowering the risk of a typo diverging one check from the others.

♻️ Suggested helper extraction
const dummyPullSecretAuthKey = "e2e-dummy.example.com"

func hasDummyEntry(data []byte) bool {
	return bytes.Contains(data, []byte(dummyPullSecretAuthKey))
}

Then reuse hasDummyEntry(...) at lines 2072, 2082, and 2094, and reference dummyPullSecretAuthKey at line 2053.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/util/util.go` around lines 2061 - 2094, Extract the repeated dummy
pull-secret check into a shared helper near the surrounding test utilities,
centralizing the marker in a named constant. Update the CP namespace check and
both guest secret checks in the existing Eventually callbacks to call the
helper, and use the constant wherever the dummy marker is logged or referenced,
including the setup at the earlier marker location.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@test/e2e/util/util.go`:
- Line 2083: Reduce the timeouts for the sequential Eventually calls in
EnsureGlobalPullSecret, including the assertion for openshift-config/pull-secret
propagation, so their combined worst-case duration does not substantially
increase the e2e test runtime. Preserve the diagnostic behavior and existing
polling interval while restoring the prior overall timeout budget.
- Around line 2061-2094: Extract the repeated dummy pull-secret check into a
shared helper near the surrounding test utilities, centralizing the marker in a
named constant. Update the CP namespace check and both guest secret checks in
the existing Eventually callbacks to call the helper, and use the constant
wherever the dummy marker is logged or referenced, including the setup at the
earlier marker location.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 65f3f336-80b8-4a76-a1f5-e55328cd8c35

📥 Commits

Reviewing files that changed from the base of the PR and between 65839bb and d5081ac.

📒 Files selected for processing (1)
  • test/e2e/util/util.go

@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 44.44%. Comparing base (0a7e9d4) to head (012db28).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #8991   +/-   ##
=======================================
  Coverage   44.44%   44.44%           
=======================================
  Files         774      774           
  Lines       96977    96977           
=======================================
  Hits        43105    43105           
  Misses      50897    50897           
  Partials     2975     2975           
Flag Coverage Δ
cmd-support 38.39% <ø> (ø)
cpo-hostedcontrolplane 47.19% <ø> (ø)
cpo-other 45.25% <ø> (ø)
hypershift-operator 54.22% <ø> (ø)
other 32.64% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cwbotbot

cwbotbot commented Jul 13, 2026

Copy link
Copy Markdown

Test Results

e2e-aws

Failed Tests

Total failed tests: 5

  • TestCreateCluster
  • TestCreateCluster/Main
  • TestCreateCluster/Main/EnsureGlobalPullSecret
  • TestCreateCluster/Main/EnsureGlobalPullSecret/Create_a_pod_which_uses_the_restricted_image,_should_succeed
  • TestCreateCluster/Main/EnsureGlobalPullSecret/Wait_for_pull_secret_synchronization_to_stabilize_across_all_nodes

e2e-aks

Failed Tests

Total failed tests: 4

  • TestCreateCluster
  • TestCreateCluster/Main
  • TestCreateCluster/Main/EnsureGlobalPullSecret
  • TestCreateCluster/Main/EnsureGlobalPullSecret/Check_if_the_on-disk_kubelet_config.json_matches_the_cluster_pull_secret

@jparrill

Copy link
Copy Markdown
Contributor Author

/test e2e-aws

@jparrill

Copy link
Copy Markdown
Contributor Author

Trying to reproduce the propagation flake issue...

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor

I have all the evidence. The analysis is clear. Here is the final report:

Test Failure Analysis Complete

Job Information

Test Failure Analysis

Error

hypershift_framework.go:518: failed to create cluster, tearing down: failed to create infra:
cannot create VPC S3 endpoint: operation error EC2: CreateVpcEndpoint, exceeded maximum number
of attempts, 11, https response error StatusCode: 503, RequestID: 9171f5a2-ac0e-4446-8d69-eb20ebe51cfe,
api error RequestLimitExceeded: Request limit exceeded. Account 820196288204 has been throttled
on ec2:CreateVpcEndpoint because it exceeded its request rate limit.

Summary

The sole failing test TestCreateClusterPrivate (1 failure out of 620 tests, 29 skipped) was unable to create its VPC S3 endpoint because AWS account 820196288204 was throttled on the ec2:CreateVpcEndpoint API. The test retried the call 11 times over ~103 seconds and received HTTP 503 RequestLimitExceeded on every attempt. This is an AWS-side rate-limit enforcement on the shared CI account, not a product or test code bug. The PR under test (#8991) only adds diagnostic logging to EnsureGlobalPullSecret in test/e2e/util/util.go — it does not touch infrastructure creation, VPC code, or TestCreateClusterPrivate. The test that the PR actually modifies (TestPullSecretUnavailable) passed in 682 seconds. All other 590 non-skipped tests passed as well.

Root Cause

AWS EC2 API rate-limiting (RequestLimitExceeded) on the shared CI account.

The HyperShift e2e suite runs ~20 tests in parallel, each creating its own HostedCluster with dedicated AWS infrastructure (VPCs, subnets, endpoints, etc.). At the moment TestCreateClusterPrivate attempted to call ec2:CreateVpcEndpoint, the shared CI AWS account (820196288204) had already exceeded its request rate limit for that API — likely due to the burst of parallel cluster creations from other tests in the same run (and potentially other concurrent CI jobs sharing the account).

The infrastructure creation sequence for TestCreateClusterPrivate shows it successfully created: VPC, DHCP options, internet gateway, subnet, route table, and route associations. It then failed at the VPC S3 endpoint creation step. The AWS SDK retried 11 times (the configured maximum) with backoff, but the throttling persisted for the entire retry window, resulting in the test failing after 103 seconds.

This failure is not related to PR #8991. The PR modifies only test/e2e/util/util.go to add diagnostic logging to the EnsureGlobalPullSecret function used by TestPullSecretUnavailable. No infrastructure creation code, VPC endpoint code, or TestCreateClusterPrivate code was changed. The test that the PR targets (TestPullSecretUnavailable) passed successfully.

Recommendations
  1. Retest the PR — This failure is an AWS infrastructure flake unrelated to the code changes. A /retest should pass.
  2. No code changes needed — The PR's changes to EnsureGlobalPullSecret logging are orthogonal to the TestCreateClusterPrivate infrastructure creation path.
  3. Known flake patternRequestLimitExceeded on VPC endpoint creation in the shared CI account is a known transient issue when many parallel tests hit AWS API limits simultaneously.
Evidence
Evidence Detail
Failing test TestCreateClusterPrivate — 1 of 620 tests failed (103.31s)
Error type AWS EC2 RequestLimitExceeded — HTTP 503 on ec2:CreateVpcEndpoint
AWS Account 820196288204 (shared CI account)
Retry attempts 11 (maximum reached)
RequestID 9171f5a2-ac0e-4446-8d69-eb20ebe51cfe
PR changes test/e2e/util/util.go only — adds logging to EnsureGlobalPullSecret
PR target test TestPullSecretUnavailablePASSED (682.53s)
Other tests 590 passed, 29 skipped, 0 other failures
Infra log VPC, DHCP, IGW, subnet, route table all created successfully before throttle hit
Related test TestCreateClusterPrivateWithRouteKASPASSED (1771.83s), same private-cluster pattern
Test parallelism 20 concurrent tests creating clusters, causing burst AWS API load

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 15, 2026
@jparrill jparrill changed the title OCPBUGS-98465: add diagnostic logging to pull secret propagation test fix(OCPBUGS-98465): prevent DS crash in pull secret verifier and add propagation diagnostics Jul 15, 2026
@openshift-ci-robot openshift-ci-robot removed the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 15, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@jparrill: No Jira issue is referenced in the title of this pull request.
To reference a jira issue, add 'XYZ-NNN:' to the title of this pull request and request another refresh with /jira refresh.

Details

In response to this:

Summary

  • Prevent the kubelet-config-verifier DaemonSet pod from crashing when the on-disk pull secret hasn't been synced yet
  • Move hash verification from a one-shot shell script to Go with PodExecOptions + wait.PollUntilContextTimeout
  • Separate DS verification into its own subtest so failures are attributed correctly
  • Add diagnostic logging to the propagation Eventually loops

Fixes

Root Cause

Two distinct failure modes were being reported under the same test name ("propagation timeout"):

  1. DS crash (most frequent): The verifier pod did a one-shot hash comparison (md5sum node vs cluster) and called exit 1 if they didn't match. The global-pull-secret-syncer DaemonSet takes ~30s to propagate changes to disk. If the verifier pod started before the syncer finished, the hash check failed → pod crashed → DS stuck at 2/3 ready → 20min timeout. Evidence: build 2077041516299161600 (Jul 14) — propagation completed at line 2159-2161 but DS stuck at 2/3 for 1205s.

  2. Actual propagation timeout (rare): The openshift-config/pull-secret never received the dummy entry. Evidence: build 2075336365863604224 (Jul 9) — KAS i/o timeout on the guest cluster.

Both were reported as the same 1205s "propagation" failure because the DS readiness wait was inside the propagation subtest.

Changes

Component Before After
DS pod Shell script verifies hashes, exit 1 on mismatch → crash sleep 1800 — always stays running, cleaned up by t.Cleanup
Hash verification Inside pod (one-shot, no retry) From Go via PodExecOptions + PollUntilContextTimeout (5min, 10s)
Test structure DS verification inside propagation subtest Separate subtest "Check if the on-disk kubelet config.json matches the cluster pull secret"
Propagation logging Errors silently swallowed Stage-by-stage: CP namespace sync check + guest Get() error logging
DS lifecycle Raw Create (409 on stale) + inline cleanup Stale cleanup before create + t.Cleanup (always runs)

Test plan

  • go vet passes
  • make lint — 0 issues
  • make verify — clean
  • e2e-aws-ovn periodic: DS should always reach 3/3 ready (no more crash on hash mismatch)
  • If propagation actually fails, logs will show WHERE it stalled (CP namespace vs guest unreachable vs HCCO not reconciled)

🤖 Generated with Claude Code

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot openshift-ci-robot removed the jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. label Jul 15, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/e2e/util/globalps.go`:
- Around line 34-37: Update CreateKubeletConfigVerifierDaemonSet to delete both
the existing verifier DaemonSet and the pull-secret before recreating them. Wait
until each deletion returns NotFound before proceeding with creation, ensuring
old pods and Secret data cannot race or persist into the new verifier.
- Around line 205-218: Update the shell command in the
wait.PollUntilContextTimeout callback to validate each md5sum invocation before
comparing hashes. Preserve the existing node and cluster hash output, but
capture or otherwise check both md5sum exit statuses so any failed command
causes the poll attempt to fail rather than allowing empty hashes to compare
equal.

In `@test/e2e/util/util.go`:
- Around line 2117-2122: Move the “Check if the on-disk kubelet config.json
matches the cluster pull secret” subtest and its
VerifyKubeletConfigWithDaemonSet call inside the pull-secret mutation subtest,
before its t.Cleanup restoration runs. Ensure it is skipped together with the
mutation when CPOAtLeast does not apply, and keep cleanup after the
verification.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 27a86c26-5724-48bd-9707-ecdaa5bd8fc8

📥 Commits

Reviewing files that changed from the base of the PR and between d5081ac and 37d2735.

📒 Files selected for processing (2)
  • test/e2e/util/globalps.go
  • test/e2e/util/util.go

Comment thread test/e2e/util/globalps.go
Comment on lines +34 to +37
// CreateKubeletConfigVerifierDaemonSet creates a DaemonSet that mounts the
// kubelet config directory on each node. The pod stays running so the test
// can exec into it to compare the on-disk pull secret against the cluster's.
// Stale resources from a previous failed run are cleaned up before creation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files test/e2e/util/globalps.go
wc -l test/e2e/util/globalps.go
sed -n '1,240p' test/e2e/util/globalps.go

Repository: openshift/hypershift

Length of output: 8887


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "CreateKubeletConfigVerifierDaemonSet|KubeletConfigVerifierNamespace|pull-secret" test/e2e/util/globalps.go
printf '\n---\n'
rg -n "wait.*NotFound|IsNotFound|Delete\\(ctx, .*DaemonSet|Delete\\(ctx, .*Secret" test/e2e/util -g '*.go'

Repository: openshift/hypershift

Length of output: 3507


Delete stale verifier resources before recreating them. Delete both the DaemonSet and pull-secret first, then wait for NotFound; otherwise the recreate can race the old DaemonSet’s termination and a leftover Secret can keep stale pull-secret bytes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/util/globalps.go` around lines 34 - 37, Update
CreateKubeletConfigVerifierDaemonSet to delete both the existing verifier
DaemonSet and the pull-secret before recreating them. Wait until each deletion
returns NotFound before proceeding with creation, ensuring old pods and Secret
data cannot race or persist into the new verifier.

Comment thread test/e2e/util/globalps.go Outdated
Comment thread test/e2e/util/util.go Outdated
@jparrill

Copy link
Copy Markdown
Contributor Author

Rebased with fixed Konflux pipelines

@jparrill

Copy link
Copy Markdown
Contributor Author

Root cause confirmed with @csrwng:

The ignition server rewrites the pull secret as compact JSON when bootstrapping the node. HCCO syncs kube-system/original-pull-secret in whatever format the user supplied (often pretty-printed with trailing newlines). The syncer handles this correctly via bytes.TrimRight(content, "\n") (#7638). But the verifier's readiness probe compared raw md5sum hashes without trimming — the trailing newline from ignition caused a permanent hash mismatch even though the syncer considered them equal.

Fix: tr -d '\n' before md5sum in both the readiness probe and the logging loop, matching the syncer's comparison behavior. The waitForDaemonSetRollout and UpdatedNumberScheduled checks remain as they ensure the syncer is fully rolled out before the verifier starts comparing.

@csrwng csrwng left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates! One suggestion on the newline handling in the verifier probe.

Comment thread test/e2e/util/globalps.go Outdated
Comment on lines +82 to +83
`node=$(tr -d '\n' < %s 2>/dev/null | md5sum | cut -d' ' -f1 || echo UNAVAILABLE) && `+
`cluster=$(tr -d '\n' < /etc/pull-secret/config.json 2>/dev/null | md5sum | cut -d' ' -f1 || echo UNAVAILABLE) && `+

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tr -d '\n' strips all newlines from the file (interior ones included), not just trailing ones. This doesn't match what the global-pull-secret-syncer does — it uses bytes.TrimRight(content, "\n") which only removes trailing newlines.

In practice tr -d happens to work because both files get the same treatment, but it could mask a genuinely corrupted file. The POSIX-equivalent of TrimRight is command substitution (which strips trailing newlines) + printf '%s' (which outputs without adding one):

Suggested change
`node=$(tr -d '\n' < %s 2>/dev/null | md5sum | cut -d' ' -f1 || echo UNAVAILABLE) && `+
`cluster=$(tr -d '\n' < /etc/pull-secret/config.json 2>/dev/null | md5sum | cut -d' ' -f1 || echo UNAVAILABLE) && `+
`node=$(printf '%%s' "$(cat %s 2>/dev/null)" | md5sum | cut -d' ' -f1 || echo UNAVAILABLE) && `+
`cluster=$(printf '%%s' "$(cat /etc/pull-secret/config.json 2>/dev/null)" | md5sum | cut -d' ' -f1 || echo UNAVAILABLE) && `+

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — switched to printf "%s" "$(cat FILE)" which strips only trailing newlines (shell command substitution behavior), matching the syncer's bytes.TrimRight semantics.

Comment thread test/e2e/util/globalps.go Outdated
Comment on lines +91 to +92
fmt.Sprintf(`test "$(tr -d '\n' < %s 2>/dev/null | md5sum | cut -d' ' -f1 || echo FAIL_NODE)" = "$(tr -d '\n' < /etc/pull-secret/config.json 2>/dev/null | md5sum | cut -d' ' -f1 || echo FAIL_CLUSTER)"`,
NodePullSecretPath)},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here — use printf '%%s' "$(cat ...)" to match the syncer's bytes.TrimRight(content, "\n") semantics:

Suggested change
fmt.Sprintf(`test "$(tr -d '\n' < %s 2>/dev/null | md5sum | cut -d' ' -f1 || echo FAIL_NODE)" = "$(tr -d '\n' < /etc/pull-secret/config.json 2>/dev/null | md5sum | cut -d' ' -f1 || echo FAIL_CLUSTER)"`,
NodePullSecretPath)},
fmt.Sprintf(`test "$(printf '%%s' "$(cat %s 2>/dev/null)" | md5sum | cut -d' ' -f1 || echo FAIL_NODE)" = "$(printf '%%s' "$(cat /etc/pull-secret/config.json 2>/dev/null)" | md5sum | cut -d' ' -f1 || echo FAIL_CLUSTER)"`,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — same change applied to the readiness probe.

@csrwng

csrwng commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Jul 21, 2026
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling tests matching the pipeline_run_if_changed or not excluded by pipeline_skip_if_only_changed parameters:
/test e2e-aks-4-22
/test e2e-aws-4-22
/test e2e-aks
/test e2e-aws
/test e2e-aws-upgrade-hypershift-operator
/test e2e-azure-v2-self-managed
/test e2e-kubevirt-aws-ovn-reduced
/test e2e-v2-aws
/test e2e-v2-gke

@jparrill

Copy link
Copy Markdown
Contributor Author

/retest-required

…ut, and trim trailing newlines in probe

The kubelet-config-verifier DaemonSet had three issues:

1. It copied openshift-config/pull-secret to kube-system at creation time.
   When the preceding propagation subtest restored the pull secret via
   t.Cleanup, the snapshot became stale. Fix: mount
   kube-system/original-pull-secret — the same secret the
   global-pull-secret-syncer uses as its source.

2. When HCCO updates original-pull-secret, it recalculates the configSeed
   hash and triggers a syncer DS pod restart. The verifier started checking
   hashes while the syncer was still restarting and had not yet written the
   updated content to disk. Fix: wait for the syncer DS rollout to complete
   (all pods updated and ready) before deploying the verifier.

3. The ignition server writes /var/lib/kubelet/config.json as compact JSON
   while HCCO syncs original-pull-secret in the user-supplied format (often
   pretty-printed with trailing newlines). The syncer handles this via
   bytes.TrimRight (openshift#7638), but the verifier compared raw md5sums — the
   trailing newline caused a permanent hash mismatch. Fix: use
   printf '%s' "$(cat FILE)" to strip trailing newlines before hashing,
   matching the syncer's bytes.TrimRight semantics.

Additional changes:
- Move t.Cleanup for pull secret restore to the parent test so the dummy
  entry stays in place during the verifier subtest.
- Add fallback sentinels to the readiness probe so missing files produce
  distinct hashes instead of empty-string false positives.
- Add UpdatedNumberScheduled check to waitForDaemonSetReady to detect
  in-progress rollouts.
- Separate the DS readiness check into its own subtest for correct failure
  attribution.
- Add stage-by-stage diagnostic logging to the propagation wait.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>
@openshift-ci openshift-ci Bot removed the lgtm Indicates that a PR is ready to be merged. label Jul 21, 2026
@csrwng

csrwng commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Jul 21, 2026
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling tests matching the pipeline_run_if_changed or not excluded by pipeline_skip_if_only_changed parameters:
/test e2e-aks-4-22
/test e2e-aws-4-22
/test e2e-aks
/test e2e-aws
/test e2e-aws-upgrade-hypershift-operator
/test e2e-azure-v2-self-managed
/test e2e-kubevirt-aws-ovn-reduced
/test e2e-v2-aws
/test e2e-v2-gke
/test unit
/test verify

@jparrill

Copy link
Copy Markdown
Contributor Author

/verified by e2e

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Jul 21, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@jparrill: This PR has been marked as verified by e2e.

Details

In response to this:

/verified by e2e

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@jparrill

Copy link
Copy Markdown
Contributor Author

/retest-required

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

/retest-required

Remaining retests: 0 against base HEAD 6919168 and 2 for PR HEAD 012db28 in total

@jparrill

Copy link
Copy Markdown
Contributor Author

/retest-required

@jparrill

Copy link
Copy Markdown
Contributor Author

/override e2e-kubevirt-aws-ovn-reduced

@openshift-ci

openshift-ci Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

@jparrill: /override requires failed status contexts, check run or a prowjob name to operate on.
The following unknown contexts/checkruns were given:

  • e2e-kubevirt-aws-ovn-reduced

Only the following failed contexts/checkruns were expected:

  • CodeRabbit
  • ci/prow/e2e-aks
  • ci/prow/e2e-aks-4-22
  • ci/prow/e2e-aws
  • ci/prow/e2e-aws-4-22
  • ci/prow/e2e-aws-upgrade-hypershift-operator
  • ci/prow/e2e-azure-v2-self-managed
  • ci/prow/e2e-kubevirt-aws-ovn-reduced
  • ci/prow/e2e-v2-aws
  • ci/prow/e2e-v2-gke
  • ci/prow/images
  • ci/prow/okd-scos-images
  • ci/prow/security
  • ci/prow/unit
  • ci/prow/verify
  • ci/prow/verify-deps
  • envtest-kube / Envtest Vanilla Kube ${{ matrix.version }}
  • envtest-ocp / Envtest OCP (K8s ${{ matrix.version }})
  • pull-ci-openshift-hypershift-main-e2e-aks
  • pull-ci-openshift-hypershift-main-e2e-aks-4-22
  • pull-ci-openshift-hypershift-main-e2e-aws
  • pull-ci-openshift-hypershift-main-e2e-aws-4-22
  • pull-ci-openshift-hypershift-main-e2e-aws-upgrade-hypershift-operator
  • pull-ci-openshift-hypershift-main-e2e-azure-v2-self-managed
  • pull-ci-openshift-hypershift-main-e2e-kubevirt-aws-ovn-reduced
  • pull-ci-openshift-hypershift-main-e2e-v2-aws
  • pull-ci-openshift-hypershift-main-e2e-v2-gke
  • pull-ci-openshift-hypershift-main-images
  • pull-ci-openshift-hypershift-main-okd-scos-images
  • pull-ci-openshift-hypershift-main-security
  • pull-ci-openshift-hypershift-main-unit
  • pull-ci-openshift-hypershift-main-verify
  • pull-ci-openshift-hypershift-main-verify-deps
  • tide

If you are trying to override a checkrun that has a space in it, you must put a double quote on the context.

Details

In response to this:

/override e2e-kubevirt-aws-ovn-reduced

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@jparrill

Copy link
Copy Markdown
Contributor Author

/override ci/prow/e2e-kubevirt-aws-ovn-reduced

@openshift-ci

openshift-ci Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

@jparrill: Overrode contexts on behalf of jparrill: ci/prow/e2e-kubevirt-aws-ovn-reduced

Details

In response to this:

/override ci/prow/e2e-kubevirt-aws-ovn-reduced

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@openshift-ci

openshift-ci Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

@jparrill: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@openshift-merge-bot
openshift-merge-bot Bot merged commit ddd94e5 into openshift:main Jul 22, 2026
42 of 43 checks passed
@openshift-ci-robot

Copy link
Copy Markdown

@jparrill: Jira Issue Verification Checks: Jira Issue OCPBUGS-98465
✔️ This pull request was pre-merge verified.
✔️ All associated pull requests have merged.
✔️ All associated, merged pull requests were pre-merge verified.

Jira Issue OCPBUGS-98465 has been moved to the MODIFIED state and will move to the VERIFIED state when the change is available in an accepted nightly payload. 🕓

Details

In response to this:

Summary

The kubelet-config-verifier DaemonSet was timing out (0/3 ready for 20 minutes) across multiple platforms. Root cause confirmed empirically with @csrwng and @devguyio:

The ignition server rewrites the pull secret as compact JSON, while HCCO syncs original-pull-secret in whatever format the user supplied (often pretty-printed). The syncer handles this correctly — it uses bytes.TrimRight(content, "\n") before comparing (#7638). But the verifier's readiness probe compared raw md5sum hashes without trimming, so the trailing newline from ignition caused a permanent hash mismatch even though the syncer considered them equal.

Changes

  1. Trim newlines before hashing: pipe through tr -d '\n' before md5sum in both the readiness probe and the diagnostic logging loop, matching the syncer's comparison behavior
  2. Mount original-pull-secret instead of static copy: the previous approach copied openshift-config/pull-secret to kube-system at creation time, which became stale when t.Cleanup restored the original. Mounting original-pull-secret directly tracks the live feature state
  3. Move t.Cleanup to parent test: the pull secret restore now runs after all subtests complete, so the dummy entry stays in place during the verifier subtest
  4. Wait for syncer DS rollout: waitForDaemonSetRollout ensures syncer pods are fully updated and ready before deploying the verifier
  5. Add fallback sentinels: || echo FAIL_NODE / || echo FAIL_CLUSTER prevents false positives from empty strings when files are missing
  6. Add UpdatedNumberScheduled check: waitForDaemonSetReady now detects in-progress rollouts
  7. Separate DS readiness into own subtest: correct failure attribution
  8. Stage-by-stage diagnostic logging: propagation wait logs which stage is pending

Root Cause

Confirmed by @csrwng on a live cluster: the ignition server writes /var/lib/kubelet/config.json as trimmed compact JSON. HCCO creates kube-system/original-pull-secret from the user-supplied pull secret, which may be pretty-printed with trailing newlines. The syncer's bytes.TrimRight makes them compare equal, so it doesn't rewrite the on-disk file. The verifier's raw md5sum sees different bytes → readiness probe never passes.

Fixes

Test plan

  • Root cause confirmed empirically on live 4.22 HostedCluster
  • @csrwng verified the ignition rewrite behavior locally
  • e2e-aws presubmit passes (TestCreateCluster/Main/EnsureGlobalPullSecret)
  • e2e-aws-4-22 presubmit passes
  • e2e-aks presubmit passes

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-merge-robot

Copy link
Copy Markdown
Contributor

Fix included in release 5.0.0-0.nightly-2026-07-22-233611

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

acknowledge-critical-fixes-only Indicates if the issuer of the label is OK with the policy. approved Indicates a PR has been approved by an approver from all required OWNERS files. area/testing Indicates the PR includes changes for e2e testing jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged. verified Signifies that the PR passed pre-merge verification criteria

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants